Skip to content

feat: rbac on function discovery#1389

Merged
sergiofilhowz merged 3 commits intomainfrom
feat/rbac-on-function-discovery
Apr 2, 2026
Merged

feat: rbac on function discovery#1389
sergiofilhowz merged 3 commits intomainfrom
feat/rbac-on-function-discovery

Conversation

@sergiofilhowz
Copy link
Copy Markdown
Contributor

@sergiofilhowz sergiofilhowz commented Apr 1, 2026

Summary by CodeRabbit

  • New Features

    • Session-aware function handlers added so invocations can receive optional session context.
    • Function listing now respects session RBAC permissions.
  • Tests

    • Added RBAC worker integration tests across Node, Python, and Rust SDKs to validate function visibility.
    • Updated engine tests to exercise the new session-aware handler interface.
  • Chores

    • Bumped browser SDK package to 0.10.0-beta.4 and simplified the README example.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
iii-website Ready Ready Preview, Comment Apr 2, 2026 0:03am
motia-docs Ready Ready Preview, Comment Apr 2, 2026 0:03am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

This PR threads an optional RBAC Session through function registration and invocation: adds session-aware handler types and registration API, updates the service macro to detect session parameters, forwards sessions into invocation and function handlers, and applies RBAC filtering in get_functions(). Tests and call sites updated accordingly.

Changes

Cohort / File(s) Summary
Macro-based Service Handler Generation
engine/function-macros/src/lib.rs
Detect non-self params, add helpers to identify Session/Option<Arc<Session>>, and emit conditional handler/registration and method calls depending on session presence.
Core Engine Infrastructure
engine/src/engine/mod.rs, engine/src/function.rs, engine/src/invocation/mod.rs
Introduce HandlerOutput, HandlerFn/SessionHandlerFn traits, SessionHandler type, expose Handler.f, add register_function_handler_with_session, propagate optional Session through invocation → function call path, and adjust middleware branching.
Engine Function Module RBAC
engine/src/modules/engine_fn/mod.rs
get_functions now accepts session and applies RBAC filtering against registration metadata when a session is present.
Mocks & Session Types
engine/src/condition.rs, engine/src/modules/worker/rbac_session.rs
MockEngine updated with register_function_handler_with_session no-op; AuthResult now derives Debug.
Test Handler Signature Updates
engine/src/modules/..., engine/tests/..., engine/tests/common/queue_helpers.rs (multiple files)
Numerous tests and test helpers updated: registered Function handler closures now accept a third session parameter (typically passed as None), call sites updated to forward None.
Queue & Adapter Tests
engine/src/modules/queue/..., engine/src/modules/telemetry/mod.rs
Handler closures in queue/telemetry tests updated to accept session param; call paths adjusted for new signature.
RBAC Worker Integration Tests
sdk/packages/node/iii/tests/rbac-workers.test.ts, sdk/packages/python/iii/tests/test_rbac_workers.py, sdk/packages/rust/iii/tests/rbac_workers.rs
Added/updated integration tests asserting listFunctions() RBAC filtering behavior for different tokens.
Config & Packaging
sdk/fixtures/config-test.yaml, sdk/packages/node/iii-browser/package.json, sdk/packages/node/iii-browser/README.md
Expose engine::functions::list in RBAC fixtures, bump iii-browser to 0.10.0-beta.4, and update README example usage.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Engine
    participant ServiceMacro
    participant HandlerRegistry
    participant InvocationHandler
    participant FunctionHandler
    participant Session as RBAC_Session

    Client->>Engine: register_function_handler_with_session(SessionHandler)
    Engine->>HandlerRegistry: store SessionHandler

    Client->>Engine: invoke_function(input, maybe_session)
    Engine->>InvocationHandler: handle_invocation(input, session)

    alt session provided
        InvocationHandler->>Session: clone session
        InvocationHandler->>FunctionHandler: call_handler(invocation_id, input, Some(session))
        FunctionHandler->>FunctionHandler: user_handler(input, Some(session))
    else no session
        InvocationHandler->>FunctionHandler: call_handler(invocation_id, input, None)
        FunctionHandler->>FunctionHandler: user_handler(input, None)
    end

    FunctionHandler-->>Engine: FunctionResult

    alt get_functions with session
        Client->>Engine: get_functions(session)
        Engine->>HandlerRegistry: filter by RBAC using session
        HandlerRegistry-->>Engine: allowed_functions
        Engine-->>Client: filtered_function_list
    else get_functions without session
        Engine-->>Client: all_functions
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • guibeira
  • ytallo

Poem

🐇 I hop through code with a curious grin,
Sessions in pockets, permissions locked in,
Macros that spy for a Session clue,
Handlers now know what each user can do,
Hooray — RBAC checks, and the rabbit hops on!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: rbac on function discovery' is specific and directly related to the main changeset focus: extending RBAC (Role-Based Access Control) to filter function discovery via the list_functions() API.
Docstring Coverage ✅ Passed Docstring coverage is 89.06% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rbac-on-function-discovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mintlify
Copy link
Copy Markdown

mintlify bot commented Apr 1, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
iii 🟢 Ready View Preview Apr 1, 2026, 8:55 PM

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
engine/src/engine/mod.rs (1)

672-754: ⚠️ Potential issue | 🔴 Critical

Don't bypass the normal dispatch path after middleware.

This branch returns before the match action logic and invokes remember_invocation() directly. With middleware enabled, TriggerAction::Enqueue becomes an immediate invoke, TriggerAction::Void still sends an InvocationResult, and the _caller_worker_id injection from spawn_invoke_function() is lost. Please feed the enriched payload back through the same post-action dispatch path so middleware doesn't change invocation semantics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/engine/mod.rs` around lines 672 - 754, The middleware branch is
bypassing the normal dispatch path by calling remember_invocation() directly
(losing semantics for TriggerAction::Enqueue, TriggerAction::Void and the
_caller_worker_id set by spawn_invoke_function()); instead, after obtaining
enriched_payload from engine.call(&middleware_id, ...), re-enter the same
post-action dispatch logic used elsewhere (the match action branch) so the
enriched payload flows through the exact same code path that handles
Enqueue/Void/normal invokes and preserves _caller_worker_id; in practice replace
the direct call to remember_invocation/send_msg in the tokio::spawn with code
that reuses the existing dispatch mechanism (or call the same helper used by
spawn_invoke_function) passing enriched_payload, inv_id, function_id,
traceparent and baggage so behavior remains identical to non-middleware
invocations.
🧹 Nitpick comments (1)
engine/src/function.rs (1)

39-45: Avoid cloning every payload in call_handler.

data is already owned here and isn't reused after dispatch, so data.clone() adds a full extra JSON copy to every invocation on the hot path.

♻️ Proposed fix
     pub async fn call_handler(
         self,
         invocation_id: Option<Uuid>,
         data: Value,
         session: Option<Arc<Session>>,
     ) -> FunctionResult<Option<Value>, ErrorBody> {
-        (self.handler)(invocation_id, data.clone(), session).await
+        (self.handler)(invocation_id, data, session).await
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/function.rs` around lines 39 - 45, call_handler is cloning the
owned JSON payload unnecessarily; instead of calling
(self.handler)(invocation_id, data.clone(), session).await, pass the owned data
directly: (self.handler)(invocation_id, data, session).await. Update this call
in the call_handler function (and if the handler signature currently expects a
reference, adjust the handler/type to accept Value by value or otherwise move
the value) so no extra JSON copy is performed on the hot path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/function-macros/src/lib.rs`:
- Around line 163-183: The helper type_contains_ident is too permissive: before
generating the SessionHandler closure you must validate the function's second
parameter shape exactly (not just that it contains the identifier "Session");
update has_session_param or add an explicit check right before constructing
SessionHandler to accept only Option<::std::sync::Arc<Session>> (or the exact
allowed variants) and otherwise emit a compile_error! with a clear message;
reference the symbols type_contains_ident, has_session_param, and SessionHandler
so the check is placed just before the SessionHandler generation block and
rejects/diagnoses unsupported shapes rather than letting the generated closure
produce opaque type errors.

---

Outside diff comments:
In `@engine/src/engine/mod.rs`:
- Around line 672-754: The middleware branch is bypassing the normal dispatch
path by calling remember_invocation() directly (losing semantics for
TriggerAction::Enqueue, TriggerAction::Void and the _caller_worker_id set by
spawn_invoke_function()); instead, after obtaining enriched_payload from
engine.call(&middleware_id, ...), re-enter the same post-action dispatch logic
used elsewhere (the match action branch) so the enriched payload flows through
the exact same code path that handles Enqueue/Void/normal invokes and preserves
_caller_worker_id; in practice replace the direct call to
remember_invocation/send_msg in the tokio::spawn with code that reuses the
existing dispatch mechanism (or call the same helper used by
spawn_invoke_function) passing enriched_payload, inv_id, function_id,
traceparent and baggage so behavior remains identical to non-middleware
invocations.

---

Nitpick comments:
In `@engine/src/function.rs`:
- Around line 39-45: call_handler is cloning the owned JSON payload
unnecessarily; instead of calling (self.handler)(invocation_id, data.clone(),
session).await, pass the owned data directly: (self.handler)(invocation_id,
data, session).await. Update this call in the call_handler function (and if the
handler signature currently expects a reference, adjust the handler/type to
accept Value by value or otherwise move the value) so no extra JSON copy is
performed on the hot path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a7bada93-39ae-4846-8dc0-c7964144760d

📥 Commits

Reviewing files that changed from the base of the PR and between 632c2b8 and d1fcbce.

📒 Files selected for processing (21)
  • docs/how-to/worker-rbac.mdx
  • engine/function-macros/src/lib.rs
  • engine/src/condition.rs
  • engine/src/engine/mod.rs
  • engine/src/function.rs
  • engine/src/invocation/mod.rs
  • engine/src/modules/bridge_client/mod.rs
  • engine/src/modules/engine_fn/mod.rs
  • engine/src/modules/queue/adapters/builtin/adapter.rs
  • engine/src/modules/queue/queue.rs
  • engine/src/modules/telemetry/mod.rs
  • engine/src/modules/worker/rbac_session.rs
  • engine/tests/common/queue_helpers.rs
  • engine/tests/dlq_redrive_e2e.rs
  • engine/tests/queue_e2e_fanout.rs
  • engine/tests/rabbitmq_queue_integration.rs
  • sdk/fixtures/config-test.yaml
  • sdk/packages/node/iii-browser/package.json
  • sdk/packages/node/iii/tests/rbac-workers.test.ts
  • sdk/packages/python/iii/tests/test_rbac_workers.py
  • sdk/packages/rust/iii/tests/rbac_workers.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
engine/src/engine/mod.rs (1)

1303-1314: ⚠️ Potential issue | 🟠 Major

Engine::call() still strips the active session.

Line 1313 hardcodes None, so every self.call(...) inside router_msg()—notably middleware and the RBAC registration hooks—invokes session-aware handlers without the worker's session. That means register_function_handler_with_session cannot actually see fields like allowed_functions or function_registration_prefix on those paths. Please add a session-aware internal call path and use it from the worker-session branches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/engine/mod.rs` around lines 1303 - 1314, Engine::call currently
passes None for the session into self.invocations.handle_invocation (see
invocations.handle_invocation(..., None, ...)), which strips the active worker
session for all router_msg paths; add a session-aware internal call path that
forwards the active session (the worker/session variable used in the
worker-session branches) into handle_invocation and update
Engine::call/router_msg to use that path for middleware, RBAC hooks and
register_function_handler_with_session so those handlers can read
allowed_functions and function_registration_prefix from the session.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@engine/src/engine/mod.rs`:
- Around line 1303-1314: Engine::call currently passes None for the session into
self.invocations.handle_invocation (see invocations.handle_invocation(..., None,
...)), which strips the active worker session for all router_msg paths; add a
session-aware internal call path that forwards the active session (the
worker/session variable used in the worker-session branches) into
handle_invocation and update Engine::call/router_msg to use that path for
middleware, RBAC hooks and register_function_handler_with_session so those
handlers can read allowed_functions and function_registration_prefix from the
session.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 480dd6c2-2634-4901-941d-521db4cf3e1a

📥 Commits

Reviewing files that changed from the base of the PR and between d1fcbce and 240bd80.

📒 Files selected for processing (4)
  • engine/src/engine/mod.rs
  • sdk/packages/node/iii/tests/rbac-workers.test.ts
  • sdk/packages/python/iii/tests/test_rbac_workers.py
  • sdk/packages/rust/iii/tests/rbac_workers.rs
✅ Files skipped from review due to trivial changes (1)
  • sdk/packages/python/iii/tests/test_rbac_workers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/packages/node/iii/tests/rbac-workers.test.ts

engine.send_msg(&w, response).await;
});
return Ok(());
if !function_id.starts_with("engine::") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include a logger message saying this prefix is not allowed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is allowed but middleware doesn't run on those

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
engine/src/engine/mod.rs (2)

1329-1341: Consider documenting that direct call() invocations have no session context.

Direct EngineTrait::call() passes None for the session parameter. This is appropriate for internal engine operations (triggers, hooks, conditions) that aren't subject to RBAC. However, this means functions invoked directly won't receive session information even if they declare a session parameter.

Consider adding a doc comment to the call method clarifying this behavior:

/// Directly invokes a function by ID.
/// 
/// Note: Direct calls do not propagate session context (session will be `None`).
/// For session-aware invocations, use the worker-based invocation path.
async fn call(...) -> ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/engine/mod.rs` around lines 1329 - 1341, Add a doc comment to
EngineTrait::call explaining that it purposefully passes None for the session
(via the invocations.handle_invocation call) so direct/internal calls have no
session context; explicitly state “session will be None” and recommend using the
worker-based invocation path for session-aware calls so callers know why session
parameters are not populated.

218-224: Consider adding documentation for the session-aware handler feature.

Per coding guidelines for engine/** changes, this new register_function_handler_with_session API and session parameter support should be documented for SDK users. Consider updating:

  1. Rust SDK docs explaining how to declare a session parameter in #[function] annotated methods
  2. Examples showing session parameter usage: fn my_func(input: MyInput, session: Option<Arc<Session>>)
  3. Explanation of when session is available (worker connections with RBAC) vs None (direct calls, workers without session)

Would you like me to draft documentation content for the session parameter feature?

As per coding guidelines: "Check for impacts to the docs/" for engine changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/engine/mod.rs` around lines 218 - 224, Add user-facing
documentation for the new session-aware API: document the
register_function_handler_with_session function and the session parameter
semantics in the Rust SDK docs, include a small example showing a handler
signature like fn my_func(input: MyInput, session: Option<Arc<Session>>) and
note how to annotate with #[function], and describe when session is Some (worker
connections with RBAC-enabled sessions) versus None (direct calls or workers
without session). Update any relevant examples and the engine-level API docs to
reference Session, SessionHandler, and SessionHandlerFn so SDK users know how to
declare and consume the session parameter.
engine/function-macros/src/lib.rs (1)

378-416: Fully qualify the Session type in generated code for better self-containment.

The generated closure (line 381) uses ::std::sync::Arc<Session> where Session is unqualified. While this works because files using the macro with session parameters explicitly import Session (e.g., use crate::modules::worker::rbac_session::Session), the generated code relies on this implicit import requirement. Fully qualifying Session would eliminate this hidden dependency and provide clearer, more robust code.

♻️ Suggested fix
-                            let `#handler_ident` = SessionHandler::new(move |input: Value, session: Option<::std::sync::Arc<Session>>| {
+                            let `#handler_ident` = SessionHandler::new(move |input: Value, session: Option<::std::sync::Arc<crate::modules::worker::rbac_session::Session>>| {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/function-macros/src/lib.rs` around lines 378 - 416, The generated
closure uses an unqualified Session type; change the closure signature and all
references of ::std::sync::Arc<Session> inside handler_and_registration (the
SessionHandler::new invocation and any return/param types used there) to the
fully-qualified path (e.g., ::crate::modules::worker::rbac_session::Session) so
the macro output is self-contained; update both the closure parameter type and
any Arc<...> occurrences before calling
engine.register_function_handler_with_session to use the fully-qualified Session
type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@engine/function-macros/src/lib.rs`:
- Around line 378-416: The generated closure uses an unqualified Session type;
change the closure signature and all references of ::std::sync::Arc<Session>
inside handler_and_registration (the SessionHandler::new invocation and any
return/param types used there) to the fully-qualified path (e.g.,
::crate::modules::worker::rbac_session::Session) so the macro output is
self-contained; update both the closure parameter type and any Arc<...>
occurrences before calling engine.register_function_handler_with_session to use
the fully-qualified Session type.

In `@engine/src/engine/mod.rs`:
- Around line 1329-1341: Add a doc comment to EngineTrait::call explaining that
it purposefully passes None for the session (via the
invocations.handle_invocation call) so direct/internal calls have no session
context; explicitly state “session will be None” and recommend using the
worker-based invocation path for session-aware calls so callers know why session
parameters are not populated.
- Around line 218-224: Add user-facing documentation for the new session-aware
API: document the register_function_handler_with_session function and the
session parameter semantics in the Rust SDK docs, include a small example
showing a handler signature like fn my_func(input: MyInput, session:
Option<Arc<Session>>) and note how to annotate with #[function], and describe
when session is Some (worker connections with RBAC-enabled sessions) versus None
(direct calls or workers without session). Update any relevant examples and the
engine-level API docs to reference Session, SessionHandler, and SessionHandlerFn
so SDK users know how to declare and consume the session parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bb075b2e-58ba-4073-97ef-cacc8291d78e

📥 Commits

Reviewing files that changed from the base of the PR and between 240bd80 and 8fb7dbe.

📒 Files selected for processing (4)
  • engine/function-macros/src/lib.rs
  • engine/src/condition.rs
  • engine/src/engine/mod.rs
  • sdk/packages/node/iii-browser/README.md

@sergiofilhowz sergiofilhowz merged commit 009e80d into main Apr 2, 2026
23 checks passed
@sergiofilhowz sergiofilhowz deleted the feat/rbac-on-function-discovery branch April 2, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants